package com.andy.basics;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@org.springframework.stereotype.Component
@Aspect
public class Logger {

	private static final String SHIFT = "\t";

	private static final String ENTERED = "-> ";

	private static final String EXITED = "<- ";

	private static final org.apache.log4j.Logger debugLogger = org.apache.log4j.Logger.getLogger("DEBUG." + Logger.class.getName());

	private static final org.apache.log4j.Logger infoLogger = org.apache.log4j.Logger.getLogger("INFO." + Logger.class.getName());

	private static final org.apache.log4j.Logger errorLogger = org.apache.log4j.Logger.getLogger("ERROR." + Logger.class.getName());

	private static final org.apache.log4j.Logger traceLogger = org.apache.log4j.Logger.getLogger("TRACE." + Logger.class.getName());

	private int shiftCount = 0;

	private Map<Integer, String> shiftMap = new HashMap<Integer, String>();

	public Logger() {
		Logger.debugLogger.debug(this.getClass().getName() + " created...");
	}

	@Around("execution(* com.bss..*.*(..))")
	public Object logAround1(final ProceedingJoinPoint joinPoint) throws Throwable {
		return this.doLogAround(joinPoint);
	}

	@Around("execution(* com.andy..*.*(..))")
	public Object logAround2(final ProceedingJoinPoint joinPoint) throws Throwable {
		return this.doLogAround(joinPoint);
	}

	protected Object doLogAround(final ProceedingJoinPoint joinPoint) throws Throwable {
		String clazz = joinPoint.getTarget().getClass().getSimpleName();

		String fqcn = joinPoint.getTarget().getClass().getName();

		String method = joinPoint.getSignature().getName();

		int shift = ++this.shiftCount;
		String indent = this.indent(shift);

		if (org.apache.log4j.Logger.getLogger(Constants.TRACE_KEY + fqcn).isTraceEnabled()
				&& !org.apache.log4j.Logger.getLogger(Constants.TRACE_KEY + fqcn + "." + method).isTraceEnabled()) {
			this.traceMethodEntry(joinPoint, clazz, fqcn, method, false, indent);
		} else if (org.apache.log4j.Logger.getLogger(Constants.TRACE_KEY + fqcn + "." + method).isTraceEnabled()) {
			this.traceMethodEntry(joinPoint, clazz, fqcn, method, true, indent);
		}
		Object retVal = null;
		try {
			retVal = joinPoint.proceed();
		} catch (Throwable e) {
			org.apache.log4j.Logger.getLogger(Constants.ERROR_KEY + fqcn + "." + method).error(e.getMessage(), e);
			--this.shiftCount;
			throw e;
		}

		if (org.apache.log4j.Logger.getLogger(Constants.TRACE_KEY + fqcn).isTraceEnabled()
				&& !org.apache.log4j.Logger.getLogger(Constants.TRACE_KEY + fqcn + "." + method).isTraceEnabled()) {
			this.traceMethodExit(clazz, fqcn, method, false, retVal, indent);
		} else if (org.apache.log4j.Logger.getLogger(Constants.TRACE_KEY + fqcn + "." + method).isTraceEnabled()) {
			this.traceMethodExit(clazz, fqcn, method, true, retVal, indent);
		}
		--this.shiftCount;
		return retVal;
	}

	protected void debugMethodExit(String clazz, String fqcn, String method) {
		StringBuffer logMessage = new StringBuffer();

		logMessage.append(Logger.EXITED);

		logMessage.append(clazz);
		logMessage.append(".");
		logMessage.append(method);
		logMessage.append("()");

		org.apache.log4j.Logger.getLogger(Constants.DEBUG_KEY + fqcn + "." + method).debug(logMessage);
	}

	protected void traceMethodExit(String clazz, String fqcn, String method, boolean specific, Object retVal, String indent) {
		StringBuffer logMessage = new StringBuffer();

		logMessage.append(indent);
		logMessage.append(Logger.EXITED);
		logMessage.append(clazz);
		logMessage.append(".");
		logMessage.append(method);
		logMessage.append("()");

		if (specific) {
			org.apache.log4j.Logger.getLogger(Constants.TRACE_KEY + fqcn + "." + method).trace(
					logMessage + "=" + (this.printable(retVal)));
		} else {
			org.apache.log4j.Logger.getLogger(Constants.TRACE_KEY + fqcn).trace(logMessage + "=" + (this.printable(retVal)));
		}
	}

	protected Object printable(Object retVal) {
		if (retVal == null) {
			return null;
		}
		if ((retVal instanceof Collection) || (retVal instanceof Object[])) {
			return retVal.getClass().getName();
		}
		return retVal;
	}

	protected void debugMethodEntry(String clazz, String fqcn, String method) {
		StringBuffer logMessage = new StringBuffer();

		logMessage.append(Logger.ENTERED);

		logMessage.append(clazz);
		logMessage.append(".");
		logMessage.append(method);
		logMessage.append("()");

		org.apache.log4j.Logger.getLogger(Constants.DEBUG_KEY + fqcn + "." + method).debug(logMessage);
	}

	protected void traceMethodEntry(final ProceedingJoinPoint joinPoint, String clazz, String fqcn, String method,
			boolean specific, String indent) {
		StringBuffer logMessage = new StringBuffer();

		logMessage.append(indent);
		logMessage.append(Logger.ENTERED);
		logMessage.append(clazz);
		logMessage.append(".");
		logMessage.append(method);
		logMessage.append("(");

		Object[] args = joinPoint.getArgs();
		for (int i = 0; i < args.length; i++) {
			logMessage.append(this.printable(args[i])).append(",");
		}
		if (args.length > 0) {
			logMessage.deleteCharAt(logMessage.length() - 1);
		}

		logMessage.append(")");

		if (specific) {
			org.apache.log4j.Logger.getLogger(Constants.TRACE_KEY + fqcn + "." + method).trace(logMessage);
		} else {
			org.apache.log4j.Logger.getLogger(Constants.TRACE_KEY + fqcn).trace(logMessage);
		}
	}

	public org.apache.log4j.Logger getLogger(final String key, final Object source) {
		return org.apache.log4j.Logger.getLogger(key + source.getClass().getName());
	}

	public boolean isTraceEnabled(final Object source) {
		org.apache.log4j.Logger logger = this.getLogger(Constants.TRACE_KEY, source);
		return logger.isTraceEnabled();
	}

	public void trace(final Object source, final Object message) {
		org.apache.log4j.Logger logger = this.getLogger(Constants.TRACE_KEY, source);
		if (logger.isTraceEnabled()) {
			logger.trace(message);
		}
	}

	public void trace(final Object source, final Object message, final Throwable error) {
		org.apache.log4j.Logger logger = this.getLogger(Constants.TRACE_KEY, source);
		if (logger.isTraceEnabled()) {
			logger.trace(message, error);
		}
	}

	public boolean isDebugEnabled(final Object source) {
		org.apache.log4j.Logger logger = this.getLogger(Constants.DEBUG_KEY, source);
		return logger.isDebugEnabled();
	}

	public void debug(final Object source, final Object message) {
		org.apache.log4j.Logger logger = this.getLogger(Constants.DEBUG_KEY, source);
		if (logger.isDebugEnabled()) {
			logger.debug(message);
		}
	}

	public void debug(final Object source, final Object message, final Throwable error) {
		org.apache.log4j.Logger logger = this.getLogger(Constants.DEBUG_KEY, source);
		if (logger.isDebugEnabled()) {
			logger.debug(message, error);
		}
	}

	public void trace(final Object message, final Throwable throwable) {
		Logger.traceLogger.trace(message, throwable);
	}

	public void trace(final Object message) {
		Logger.traceLogger.trace(message);
	}

	public void debug(final Object message, final Throwable throwable) {
		Logger.debugLogger.debug(message, throwable);
	}

	public void debug(final Object message) {
		Logger.debugLogger.debug(message);
	}

	public String indent(Integer shift) {
		String shiftLeft = this.shiftMap.get(shift);

		if (shiftLeft == null) {
			synchronized (this) {
				if (shiftLeft == null) {
					StringBuilder sb = new StringBuilder();
					for (int i = 0; i < shift; i++) {
						sb.append(Logger.SHIFT);
					}
					shiftLeft = sb.toString();
					this.shiftMap.put(shift, shiftLeft);
				}
			}
		}
		return shiftLeft;
	}
}
